#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "NesInput.h"
#include "NesPPU.h"
#include "NesSystem.H"
#include "NesMapper.h"
#include "NesCPU6502.h"


inline static uint32_t ucNesCPUMemoryGet(uint16_t addr);
inline static void vNesCPUMemorySet(uint16_t addr, uint32_t value);
inline static uint16_t ucNesCPUMemoryWordGet(uint16_t addr);
inline static void vNesCPURegisterSet(uint8_t *reg, uint8_t value);
inline static uint8_t Pop(void);
inline static uint16_t PopWord(void);
inline static uint16_t GetInd(void);
inline static void vCPU_ADD(uint8_t value);
inline static void vCPU_CMP(uint8_t reg, uint8_t value);
inline static uint8_t vCPU_ASL(uint8_t value);
inline static uint8_t vCPU_LSR(uint8_t value);
inline static uint8_t vCPU_ROL(uint8_t value);
inline static uint8_t vCPU_ROR(uint8_t value);
inline static void vCPU_ASLAddr(void);
inline static void vCPU_LSRAddr(void);
inline static void vCPU_ROLAddr(void);
inline static void vCPU_RORAddr(void);
inline static void vCPU_JMP(uint16_t addr);
inline static void vCPU_BranchRelative(int8_t branch);


/* 2kByte */
uint8_t st_ucCPU_RAM[1024 * 2];
/* 有些Mapper使用的拓展RAM 8kByte */
uint8_t st_ucCPU_SRAM[0x2000];

NesCPUInofType st_typeNesCPUInfo = {0};


/* CPU内存 */
uint32_t cNesCPUBankAddr8kByteSet(uint32_t cSerial, uint32_t uiAddr)
{
    uint8_t *pucAddr = g_typeNesInfo.PRG_ROM + (uiAddr % g_typeNesInfo.PRG_ROMSize);

    /* 分成4块，每块8kBtye */
    switch(cSerial)
    {
        case 0 : st_typeNesCPUInfo.ROMBank0 = pucAddr; break;
        case 1 : st_typeNesCPUInfo.ROMBank1 = pucAddr; break;
        case 2 : st_typeNesCPUInfo.ROMBank2 = pucAddr; break;
        case 3 : st_typeNesCPUInfo.ROMBank3 = pucAddr; break;

        default : return 1;
    }

    return 0;
}

uint32_t cNesCPUBankAddr16kByteSet(uint32_t cSerial, uint32_t uiAddr)
{
    cNesCPUBankAddr8kByteSet((cSerial << 1),     uiAddr);
    cNesCPUBankAddr8kByteSet((cSerial << 1) + 1, uiAddr + (1024 * 8));

    return 0;
}

uint32_t cNesCPUBankAddr32kByteSet(uint32_t uiAddr)
{
    cNesCPUBankAddr8kByteSet(0, uiAddr);
    cNesCPUBankAddr8kByteSet(1, uiAddr + (1024 * 8 ));
    cNesCPUBankAddr8kByteSet(2, uiAddr + (1024 * 16));
    cNesCPUBankAddr8kByteSet(3, uiAddr + (1024 * 24));

    return 0;
}

void vNesCPUMemoryLoad(uint32_t addr, uint32_t uiAddr, uint32_t iLength)
{
    /* 加载程序代码前16kByte到CPU的第1个区域 */
    cNesCPUBankAddr16kByteSet(0, 0);
    /* 加载程序代码后16kByte到CPU的第2个区域 */
    cNesCPUBankAddr16kByteSet(1, g_typeNesInfo.PRG_ROMSize - (1024 * 16));
}

inline static uint32_t ucNesCPUMemoryGet(uint16_t addr)
{
    uint32_t ucValue;

    /* addr / 0x2000 : 8kByte */
    switch(addr >> 13)
    {
        /* 0x0000 - 0x1FFF: 4块空间，后面3块是前面1块的镜像 */
        case 0 : ucValue = st_typeNesCPUInfo.RAM[addr & 0x07FF]; break;

        /* 0x2000 - 0x3FFF */
        case 1 : ucValue = ucNesPPURegisterGet(addr); break;

        /* 0x4000 - 0x5FFF */
        case 2 :
            if(addr < 0x4018)
            {
                if(addr == 0x4016)
                    ucValue = ucNesInputBitGet(NES_JOYPAD1);
                else if(addr == 0x4017)
                    ucValue = ucNesInputBitGet(NES_JOYPAD2);
            }
            break;

        /* 0x6000 - 0x7FFF */
        case 3 : ucValue = st_typeNesCPUInfo.SRAM[addr & 0x1FFF]; break;

        /* 0x8000 - 0x9FFF */
        case 4 : ucValue = st_typeNesCPUInfo.ROMBank0[addr & 0x1FFF]; break;
        /* 0xA000 - 0xBFFF */
        case 5 : ucValue = st_typeNesCPUInfo.ROMBank1[addr & 0x1FFF]; break;
        /* 0xC000 - 0xDFFF */
        case 6 : ucValue = st_typeNesCPUInfo.ROMBank2[addr & 0x1FFF]; break;
        /* 0xE000 - 0xFFFF */
        case 7 : ucValue = st_typeNesCPUInfo.ROMBank3[addr & 0x1FFF]; break;

        default : ucValue = 0; break;
    }

    return ucValue;
}

inline static void vNesCPUMemorySet(uint16_t addr, uint32_t value)
{
    /* addr / 0x2000 : 8kByte */
    switch(addr >> 13)
    {
        /* 0x0000 - 0x1FFF: 4块空间，后面3块是前面1块的镜像 */
        case 0 : st_typeNesCPUInfo.RAM[addr & 0x07FF] = value; break;

        /* 0x2000 - 0x3FFF */
        case 1 : vNesPPURegisterSet(addr, value); break;

        /* 0x4000 - 0x5FFF */
        case 2 :
            if(addr < 0x4018)
            {
                if(addr == 0x4014)
                {
                    /* 模拟DMA一次性传输256字节 */
                    value <<= 8;
                    vNesPPUOamRamSet(0, (value < 0x2000) ? &st_typeNesCPUInfo.RAM[value & 0x07FF] : &st_typeNesCPUInfo.SRAM[value & 0x1FFF], 256);

                    /* 1次DMA传输需要513个时钟 */
                    st_typeNesCPUInfo.clockCnt += 513;
                    /* 奇数时钟需要额外再增加1个时钟 */
                    st_typeNesCPUInfo.clockCnt += st_typeNesCPUInfo.clockCnt & 1;
                }
                else if(addr == 0x4016)
                {
                    /* 复位输入 */
                    vNesInputReset(value);
                }
            }
            break;

        /* 0x6000 - 0x7FFF */
        case 3 : st_typeNesCPUInfo.SRAM[addr & 0x1FFF] = value;  break;

        /* Mapper 操作 */
        default : g_typeNesMapperInfo.vMapperSetFunction(addr, value); break;
    }
}

inline static uint16_t ucNesCPUMemoryWordGet(uint16_t addr)
{
    uint16_t lo = ucNesCPUMemoryGet(addr++), hi = ucNesCPUMemoryGet(addr);

    return lo | hi << 8;
}


/* CPU寄存器 */
#define CPU_A               st_typeNesCPUInfo.registers.A
#define CPU_A_Set(value)    vNesCPURegisterSet(&st_typeNesCPUInfo.registers.A, (value))

#define CPU_X               st_typeNesCPUInfo.registers.X
#define CPU_X_Set(value)    vNesCPURegisterSet(&st_typeNesCPUInfo.registers.X, (value))

#define CPU_Y               st_typeNesCPUInfo.registers.Y
#define CPU_Y_Set(value)    vNesCPURegisterSet(&st_typeNesCPUInfo.registers.Y, (value))

#define CPU_PS              st_typeNesCPUInfo.registers.PS
#define CPU_PC              st_typeNesCPUInfo.registers.PC
#define CPU_SP              st_typeNesCPUInfo.registers.SP

#define SetFlags(flag)      (st_typeNesCPUInfo.registers.PS |= ((flag) | Reserved))
#define ClearFlags(flag)    (st_typeNesCPUInfo.registers.PS &= ~(flag))
#define CheckFlag(flag)    ((st_typeNesCPUInfo.registers.PS & (flag)) == (flag))

#define CPU_Operand         st_typeNesCPUInfo.operand
#define GetOperandValue()  ((st_typeNesCPUInfo.Mode >= vCPU_Zero) ? ucNesCPUMemoryGet(CPU_Operand) : CPU_Operand)


inline static void SetZeroNegativeFlags(uint8_t value)
{
    if(value == 0)
    {
        SetFlags(Zero);
    }
    else if(value & 0x80)
    {
        SetFlags(Negative);
    }
}

inline static void vNesCPURegisterSet(uint8_t *reg, uint8_t value)
{
    ClearFlags(Zero | Negative);

    SetZeroNegativeFlags(value);

    *reg = value;
}

inline static void Push(uint8_t value)
{
    vNesCPUMemorySet(0x100 + CPU_SP, value);
    --CPU_SP;
}

inline static void PushWord(uint16_t value)
{
    Push(value >> 8);

    Push(value);
}

inline static uint8_t Pop(void)
{
    ++CPU_SP;
    return ucNesCPUMemoryGet(0x100 + CPU_SP);
}

inline static uint16_t PopWord(void)
{
    uint16_t lo = Pop(), hi = Pop();

    return lo | hi << 8;
}

inline static uint16_t GetInd(void)
{
    if((CPU_Operand & 0xFF) == 0xFF)
    {
        uint16_t lo = ucNesCPUMemoryGet(CPU_Operand);
        uint16_t hi = ucNesCPUMemoryGet(CPU_Operand - 0xFF);
        return lo | hi << 8;
    }

    return ucNesCPUMemoryWordGet(CPU_Operand);
}


/* CPU指令 */
inline static void vCPU_ADD(uint8_t value)
{
    uint16_t result = (uint16_t)CPU_A + (uint16_t)value + (CheckFlag(Carry) ? Carry : 0x00);

    ClearFlags(Carry | Negative | Overflow | Zero);
    SetZeroNegativeFlags(result);

    if(~(CPU_A ^ value) & (CPU_A ^ result) & 0x80)
        SetFlags(Overflow);

    if(result > 0xFF)
        SetFlags(Carry);

    CPU_A_Set(result);
}

inline static void vCPU_CMP(uint8_t reg, uint8_t value)
{
    uint8_t result = reg - value;

    ClearFlags(Carry | Negative | Zero);

    if(reg >= value)
        SetFlags(Carry);

    if(reg == value)
        SetFlags(Zero);

    if(result & 0x80)
        SetFlags(Negative);
}

inline static uint8_t vCPU_ASL(uint8_t value)
{
    uint8_t result = value << 1;

    ClearFlags(Carry | Negative | Zero);

    if(value & 0x80)
        SetFlags(Carry);

    SetZeroNegativeFlags(result);

    return result;
}

inline static uint8_t vCPU_LSR(uint8_t value)
{
    uint8_t result = value >> 1;

    ClearFlags(Carry | Negative | Zero);

    if(value & 0x01)
        SetFlags(Carry);

    SetZeroNegativeFlags(result);

    return result;
}

inline static uint8_t vCPU_ROL(uint8_t value)
{
    uint8_t result = (value << 1 | (CheckFlag(Carry) ? 0x01 : 0x00));

    ClearFlags(Carry | Negative | Zero);

    if(value & 0x80)
        SetFlags(Carry);

    SetZeroNegativeFlags(result);

    return result;
}

inline static uint8_t vCPU_ROR(uint8_t value)
{
    uint8_t result = (value >> 1 | (CheckFlag(Carry) ? 0x80 : 0x00));

    ClearFlags(Carry | Negative | Zero);

    if(value & 0x01)
        SetFlags(Carry);

    SetZeroNegativeFlags(result);

    return result;
}

inline static void vCPU_ASLAddr(void)
{
    vNesCPUMemorySet(CPU_Operand, vCPU_ASL(ucNesCPUMemoryGet(CPU_Operand)));
}

inline static void vCPU_LSRAddr(void)
{
    vNesCPUMemorySet(CPU_Operand, vCPU_LSR(ucNesCPUMemoryGet(CPU_Operand)));
}

inline static void vCPU_ROLAddr(void)
{
    vNesCPUMemorySet(CPU_Operand, vCPU_ROL(ucNesCPUMemoryGet(CPU_Operand)));
}

inline static void vCPU_RORAddr(void)
{
    vNesCPUMemorySet(CPU_Operand, vCPU_ROR(ucNesCPUMemoryGet(CPU_Operand)));
}

inline static void vCPU_JMP(uint16_t addr)
{
    CPU_PC = addr;
}

inline static void vCPU_BranchRelative(int8_t branch)
{
    if(branch)
    {
//        /* 额外增加1个CPU时钟 */
//        st_typeNesCPUInfo.clockCnt += 1;

        /* 跨页也需要额外增加1个CPU时钟 */
        st_typeNesCPUInfo.clockCnt += ((CPU_Operand ^ CPU_PC) >> 8) & 1;

        CPU_PC += (int8_t)CPU_Operand;
    }
}


//OP Codes
void vCPU_AND(void)         { CPU_A_Set(CPU_A & GetOperandValue()); }
void vCPU_EOR(void)         { CPU_A_Set(CPU_A ^ GetOperandValue()); }
void vCPU_ORA(void)         { CPU_A_Set(CPU_A | GetOperandValue()); }
void vCPU_ADC(void)         { vCPU_ADD(GetOperandValue()); }
void vCPU_SBC(void)         { vCPU_ADD(GetOperandValue() ^ 0xFF); }
void vCPU_CPA(void)         { vCPU_CMP(CPU_A, GetOperandValue()); }
void vCPU_CPX(void)         { vCPU_CMP(CPU_X, GetOperandValue()); }
void vCPU_CPY(void)         { vCPU_CMP(CPU_Y, GetOperandValue()); }

void vCPU_INC(void)
{
    uint32_t value = ucNesCPUMemoryGet(CPU_Operand) + 1;

    ClearFlags(Negative | Zero);
    SetZeroNegativeFlags(value);
    vNesCPUMemorySet(CPU_Operand, value);
}

void vCPU_DEC(void)
{
    uint32_t value = ucNesCPUMemoryGet(CPU_Operand) - 1;

    ClearFlags(Negative | Zero);
    SetZeroNegativeFlags(value);
    vNesCPUMemorySet(CPU_Operand, value);
}

void vCPU_BIT(void)
{
    uint8_t value = GetOperandValue();

    ClearFlags(Zero | Overflow | Negative);

    if((CPU_A & value) == 0)
        SetFlags(Zero);

    if(value & 0x40)
        SetFlags(Overflow);

    if(value & 0x80)
        SetFlags(Negative);
}

void vCPU_LDA(void)         { CPU_A_Set(GetOperandValue()); }
void vCPU_LDX(void)         { CPU_X_Set(GetOperandValue()); }
void vCPU_LDY(void)         { CPU_Y_Set(GetOperandValue()); }
void vCPU_STA(void)         { vNesCPUMemorySet(CPU_Operand, CPU_A); }
void vCPU_STX(void)         { vNesCPUMemorySet(CPU_Operand, CPU_X); }
void vCPU_STY(void)         { vNesCPUMemorySet(CPU_Operand, CPU_Y); }
void vCPU_TAX(void)         { CPU_X_Set(CPU_A); }
void vCPU_TAY(void)         { CPU_Y_Set(CPU_A); }
void vCPU_TSX(void)         { CPU_X_Set(CPU_SP); }
void vCPU_TXA(void)         { CPU_A_Set(CPU_X); }
void vCPU_TXS(void)         { CPU_SP = CPU_X; }
void vCPU_TYA(void)         { CPU_A_Set(CPU_Y); }
void vCPU_PHA(void)         { Push(CPU_A); }
void vCPU_PHP(void)         { Push(CPU_PS | Break | Reserved); }
void vCPU_PLA(void)         { CPU_A_Set(Pop()); }
void vCPU_PLP(void)         { CPU_PS = Pop() | Reserved; }
void vCPU_INX(void)         { CPU_X_Set(CPU_X + 1); }
void vCPU_INY(void)         { CPU_Y_Set(CPU_Y + 1); }
void vCPU_DEX(void)         { CPU_X_Set(CPU_X - 1); }
void vCPU_DEY(void)         { CPU_Y_Set(CPU_Y - 1); }
void vCPU_ASL_Acc(void)     { CPU_A_Set(vCPU_ASL(CPU_A)); }
void vCPU_ASL_Memory(void)  { vCPU_ASLAddr(); }
void vCPU_LSR_Acc(void)     { CPU_A_Set(vCPU_LSR(CPU_A)); }
void vCPU_LSR_Memory(void)  { vCPU_LSRAddr(); }
void vCPU_ROL_Acc(void)     { CPU_A_Set(vCPU_ROL(CPU_A)); }
void vCPU_ROL_Memory(void)  { vCPU_ROLAddr(); }
void vCPU_ROR_Acc(void)     { CPU_A_Set(vCPU_ROR(CPU_A)); }
void vCPU_ROR_Memory(void)  { vCPU_RORAddr(); }
void vCPU_JMP_Abs(void)     { vCPU_JMP(CPU_Operand); }
void vCPU_JMP_Ind(void)     { vCPU_JMP(GetInd()); }
void vCPU_JSR(void)         { PushWord(CPU_PC - 1); vCPU_JMP(CPU_Operand); }
void vCPU_RTS(void)         { CPU_PC = PopWord() + 1; }
void vCPU_BCC(void)         { vCPU_BranchRelative(!CheckFlag(Carry)); }
void vCPU_BCS(void)         { vCPU_BranchRelative(CheckFlag(Carry)); }
void vCPU_BEQ(void)         { vCPU_BranchRelative(CheckFlag(Zero)); }
void vCPU_BMI(void)         { vCPU_BranchRelative(CheckFlag(Negative)); }
void vCPU_BNE(void)         { vCPU_BranchRelative(!CheckFlag(Zero)); }
void vCPU_BPL(void)         { vCPU_BranchRelative(!CheckFlag(Negative)); }
void vCPU_BVC(void)         { vCPU_BranchRelative(!CheckFlag(Overflow)); }
void vCPU_BVS(void)         { vCPU_BranchRelative(CheckFlag(Overflow)); }
void vCPU_CLC(void)         { ClearFlags(Carry); }
void vCPU_CLD(void)         { ClearFlags(Decimal); }
void vCPU_CLI(void)         { ClearFlags(Interrupt); }
void vCPU_CLV(void)         { ClearFlags(Overflow); }
void vCPU_SEC(void)         { SetFlags(Carry); }
void vCPU_SED(void)         { SetFlags(Decimal); }
void vCPU_SEI(void)         { SetFlags(Interrupt); }
void vCPU_RTI(void)         { CPU_PS = Pop() | Reserved; CPU_PC = PopWord(); }
void vCPU_NOP(void)         {}

void vCPU_SLO(void)
{
    uint8_t shiftedValue = vCPU_ASL(GetOperandValue());

    CPU_A_Set(CPU_A | shiftedValue);
    vNesCPUMemorySet(CPU_Operand, shiftedValue);
}

void vCPU_SRE(void)
{
    uint8_t shiftedValue = vCPU_LSR(GetOperandValue());

    CPU_A_Set(CPU_A ^ shiftedValue);
    vNesCPUMemorySet(CPU_Operand, shiftedValue);
}

void vCPU_RLA(void)
{
    uint8_t shiftedValue = vCPU_ROL(GetOperandValue());

    CPU_A_Set(CPU_A & shiftedValue);
    vNesCPUMemorySet(CPU_Operand, shiftedValue);
}

void vCPU_RRA(void)
{
    uint8_t shiftedValue = vCPU_ROR(GetOperandValue());

    vCPU_ADD(shiftedValue);
    vNesCPUMemorySet(CPU_Operand, shiftedValue);
}

void vCPU_SAX(void)        { vNesCPUMemorySet(CPU_Operand, CPU_A & CPU_X); }
void vCPU_LAX(void)        { uint8_t value = GetOperandValue(); CPU_X_Set(value); CPU_A_Set(value); }
void vCPU_DCP(void)        { uint8_t value = GetOperandValue() - 1; vCPU_CMP(CPU_A, value); vNesCPUMemorySet(CPU_Operand, value); }
void vCPU_ISB(void)        { uint8_t value = GetOperandValue() + 1; vCPU_ADD(value ^ 0xFF); vNesCPUMemorySet(CPU_Operand, value); }

void vCPU_AAC(void)
{
    CPU_A_Set(CPU_A & GetOperandValue());

    ClearFlags(Carry);
    if(CheckFlag(Negative))
        SetFlags(Carry);
}

void vCPU_ASR(void)
{
    CPU_A_Set(CPU_A & GetOperandValue());

    ClearFlags(Carry);
    if(CPU_A & 0x01)
        SetFlags(Carry);

    CPU_A_Set(CPU_A >> 1);
}

void vCPU_ARR(void)
{
    CPU_A_Set(((CPU_A & GetOperandValue()) >> 1) | (CheckFlag(Carry) ? 0x80 : 0x00));

    ClearFlags(Carry | Overflow);
    if(CPU_A & 0x40)
        SetFlags(Carry);

    if((CheckFlag(Carry) ? 0x01 : 0x00) ^ ((CPU_A >> 5) & 0x01))
        SetFlags(Overflow);
}

void vCPU_ATX(void)        { CPU_A_Set(GetOperandValue()); CPU_X_Set(CPU_A); CPU_A_Set(CPU_A); }

void vCPU_AXS(void)
{
    uint8_t opValue = GetOperandValue(), value = (CPU_A & CPU_X) - opValue;

    ClearFlags(Carry);
    if((CPU_A & CPU_X) >= opValue)
        SetFlags(Carry);

    CPU_X_Set(value);
}

void vCPU_SYA(void)
{
    uint8_t addrHigh = CPU_Operand >> 8, addrLow = CPU_Operand & 0xFF, value = CPU_Y & (addrHigh + 1);

    vNesCPUMemorySet((((uint16_t)(CPU_Y & (addrHigh + 1))) << 8) | addrLow, value);
}

void vCPU_SXA(void)
{
    uint8_t addrHigh = CPU_Operand >> 8, addrLow = CPU_Operand & 0xFF, value = CPU_X & (addrHigh + 1);

    vNesCPUMemorySet((((uint16_t)(CPU_X & (addrHigh + 1))) << 8) | addrLow, value);
}

void vCPU_HLT(void)        {}
void vCPU_UNK(void)        {}
void vCPU_AXA(void)        { vNesCPUMemorySet(CPU_Operand, ((CPU_Operand >> 8) + 1) & CPU_A & CPU_X); }
void vCPU_TAS(void)        { CPU_SP = CPU_X & CPU_A; vNesCPUMemorySet(CPU_Operand, CPU_SP & ((CPU_Operand >> 8) + 1)); }
void vCPU_LAS(void)        { CPU_A_Set(GetOperandValue() & CPU_SP); CPU_X_Set(CPU_A); CPU_SP = CPU_A; }

void vCPU_BRK(void)
{
    PushWord(CPU_PC + 1);

    Push(CPU_PS | Break | Reserved);
    SetFlags(Interrupt);

    if(st_typeNesCPUInfo.NMIFlag == TRUE)
    {
        CPU_PC = ucNesCPUMemoryWordGet(NMIVector);

        st_typeNesCPUInfo.NMIFlag = FALSE;
    }
    else if(st_typeNesCPUInfo.IRQFlag == TRUE)
    {
        CPU_PC = ucNesCPUMemoryWordGet(IRQVector);

        st_typeNesCPUInfo.IRQFlag = FALSE;
    }
    else if(st_typeNesCPUInfo.ResetFlag == TRUE)
    {
        CPU_PC = ucNesCPUMemoryWordGet(ResetVector);

        st_typeNesCPUInfo.ResetFlag = FALSE;
    }

    //Ensure we don't start an NMI right after running a BRK instruction (first instruction in IRQ handler must run first - needed for nmi_and_brk test)
    // st_typeNesCPUInfo.NMIFlag = FALSE;
}



void (*InstructionFunctionTable[256])(void) = {
    vCPU_BRK, vCPU_ORA,  vCPU_HLT,  vCPU_SLO,   vCPU_NOP,   vCPU_ORA,   vCPU_ASL_Memory, vCPU_SLO,   vCPU_PHP, vCPU_ORA,   vCPU_ASL_Acc, vCPU_AAC,   vCPU_NOP,     vCPU_ORA,   vCPU_ASL_Memory, vCPU_SLO,   //0
    vCPU_BPL, vCPU_ORA,  vCPU_HLT,  vCPU_SLO,   vCPU_NOP,   vCPU_ORA,   vCPU_ASL_Memory, vCPU_SLO,   vCPU_CLC, vCPU_ORA,   vCPU_NOP,     vCPU_SLO,   vCPU_NOP,     vCPU_ORA,   vCPU_ASL_Memory, vCPU_SLO,   //1
    vCPU_JSR, vCPU_AND,  vCPU_HLT,  vCPU_RLA,   vCPU_BIT,   vCPU_AND,   vCPU_ROL_Memory, vCPU_RLA,   vCPU_PLP, vCPU_AND,   vCPU_ROL_Acc, vCPU_AAC,   vCPU_BIT,     vCPU_AND,   vCPU_ROL_Memory, vCPU_RLA,   //2
    vCPU_BMI, vCPU_AND,  vCPU_HLT,  vCPU_RLA,   vCPU_NOP,   vCPU_AND,   vCPU_ROL_Memory, vCPU_RLA,   vCPU_SEC, vCPU_AND,   vCPU_NOP,     vCPU_RLA,   vCPU_NOP,     vCPU_AND,   vCPU_ROL_Memory, vCPU_RLA,   //3

    vCPU_RTI, vCPU_EOR,  vCPU_HLT,  vCPU_SRE,   vCPU_NOP,   vCPU_EOR,   vCPU_LSR_Memory, vCPU_SRE,   vCPU_PHA, vCPU_EOR,   vCPU_LSR_Acc, vCPU_ASR,   vCPU_JMP_Abs, vCPU_EOR,   vCPU_LSR_Memory, vCPU_SRE,   //4
    vCPU_BVC, vCPU_EOR,  vCPU_HLT,  vCPU_SRE,   vCPU_NOP,   vCPU_EOR,   vCPU_LSR_Memory, vCPU_SRE,   vCPU_CLI, vCPU_EOR,   vCPU_NOP,     vCPU_SRE,   vCPU_NOP,     vCPU_EOR,   vCPU_LSR_Memory, vCPU_SRE,   //5
    vCPU_RTS, vCPU_ADC,  vCPU_HLT,  vCPU_RRA,   vCPU_NOP,   vCPU_ADC,   vCPU_ROR_Memory, vCPU_RRA,   vCPU_PLA, vCPU_ADC,   vCPU_ROR_Acc, vCPU_ARR,   vCPU_JMP_Ind, vCPU_ADC,   vCPU_ROR_Memory, vCPU_RRA,   //6
    vCPU_BVS, vCPU_ADC,  vCPU_HLT,  vCPU_RRA,   vCPU_NOP,   vCPU_ADC,   vCPU_ROR_Memory, vCPU_RRA,   vCPU_SEI, vCPU_ADC,   vCPU_NOP,     vCPU_RRA,   vCPU_NOP,     vCPU_ADC,   vCPU_ROR_Memory, vCPU_RRA,   //7

    vCPU_NOP, vCPU_STA,  vCPU_NOP,  vCPU_SAX,   vCPU_STY,   vCPU_STA,   vCPU_STX,        vCPU_SAX,   vCPU_DEY, vCPU_NOP,   vCPU_TXA,     vCPU_UNK,   vCPU_STY,     vCPU_STA,   vCPU_STX,        vCPU_SAX,   //8
    vCPU_BCC, vCPU_STA,  vCPU_HLT,  vCPU_AXA,   vCPU_STY,   vCPU_STA,   vCPU_STX,        vCPU_SAX,   vCPU_TYA, vCPU_STA,   vCPU_TXS,     vCPU_TAS,   vCPU_SYA,     vCPU_STA,   vCPU_SXA,        vCPU_AXA,   //9
    vCPU_LDY, vCPU_LDA,  vCPU_LDX,  vCPU_LAX,   vCPU_LDY,   vCPU_LDA,   vCPU_LDX,        vCPU_LAX,   vCPU_TAY, vCPU_LDA,   vCPU_TAX,     vCPU_ATX,   vCPU_LDY,     vCPU_LDA,   vCPU_LDX,        vCPU_LAX,   //A
    vCPU_BCS, vCPU_LDA,  vCPU_HLT,  vCPU_LAX,   vCPU_LDY,   vCPU_LDA,   vCPU_LDX,        vCPU_LAX,   vCPU_CLV, vCPU_LDA,   vCPU_TSX,     vCPU_LAS,   vCPU_LDY,     vCPU_LDA,   vCPU_LDX,        vCPU_LAX,   //B

    vCPU_CPY, vCPU_CPA,  vCPU_NOP,  vCPU_DCP,   vCPU_CPY,   vCPU_CPA,   vCPU_DEC,        vCPU_DCP,   vCPU_INY, vCPU_CPA,   vCPU_DEX,     vCPU_AXS,   vCPU_CPY,     vCPU_CPA,   vCPU_DEC,        vCPU_DCP,   //C
    vCPU_BNE, vCPU_CPA,  vCPU_HLT,  vCPU_DCP,   vCPU_NOP,   vCPU_CPA,   vCPU_DEC,        vCPU_DCP,   vCPU_CLD, vCPU_CPA,   vCPU_NOP,     vCPU_DCP,   vCPU_NOP,     vCPU_CPA,   vCPU_DEC,        vCPU_DCP,   //D
    vCPU_CPX, vCPU_SBC,  vCPU_NOP,  vCPU_ISB,   vCPU_CPX,   vCPU_SBC,   vCPU_INC,        vCPU_ISB,   vCPU_INX, vCPU_SBC,   vCPU_NOP,     vCPU_SBC,   vCPU_CPX,     vCPU_SBC,   vCPU_INC,        vCPU_ISB,   //E
    vCPU_BEQ, vCPU_SBC,  vCPU_HLT,  vCPU_ISB,   vCPU_NOP,   vCPU_SBC,   vCPU_INC,        vCPU_ISB,   vCPU_SED, vCPU_SBC,   vCPU_NOP,     vCPU_ISB,   vCPU_NOP,     vCPU_SBC,   vCPU_INC,        vCPU_ISB    //F
};

const ModeEnum InstructionModeTable[256] = {
    vCPU_Imp, vCPU_IndX, vCPU_None, vCPU_IndX,  vCPU_Zero,  vCPU_Zero,  vCPU_Zero,       vCPU_Zero,  vCPU_Imp, vCPU_Imm,   vCPU_Acc,     vCPU_Imm,   vCPU_Abs,     vCPU_Abs,   vCPU_Abs,        vCPU_Abs,   //0
    vCPU_Rel, vCPU_IndY, vCPU_None, vCPU_IndYW, vCPU_ZeroX, vCPU_ZeroX, vCPU_ZeroX,      vCPU_ZeroX, vCPU_Imp, vCPU_AbsY,  vCPU_Imp,     vCPU_AbsYW, vCPU_AbsX,    vCPU_AbsX,  vCPU_AbsXW,      vCPU_AbsXW, //1
    vCPU_Abs, vCPU_IndX, vCPU_None, vCPU_IndX,  vCPU_Zero,  vCPU_Zero,  vCPU_Zero,       vCPU_Zero,  vCPU_Imp, vCPU_Imm,   vCPU_Acc,     vCPU_Imm,   vCPU_Abs,     vCPU_Abs,   vCPU_Abs,        vCPU_Abs,   //2
    vCPU_Rel, vCPU_IndY, vCPU_None, vCPU_IndYW, vCPU_ZeroX, vCPU_ZeroX, vCPU_ZeroX,      vCPU_ZeroX, vCPU_Imp, vCPU_AbsY,  vCPU_Imp,     vCPU_AbsYW, vCPU_AbsX,    vCPU_AbsX,  vCPU_AbsXW,      vCPU_AbsXW, //3

    vCPU_Imp, vCPU_IndX, vCPU_None, vCPU_IndX,  vCPU_Zero,  vCPU_Zero,  vCPU_Zero,       vCPU_Zero,  vCPU_Imp, vCPU_Imm,   vCPU_Acc,     vCPU_Imm,   vCPU_Abs,     vCPU_Abs,   vCPU_Abs,        vCPU_Abs,   //4
    vCPU_Rel, vCPU_IndY, vCPU_None, vCPU_IndYW, vCPU_ZeroX, vCPU_ZeroX, vCPU_ZeroX,      vCPU_ZeroX, vCPU_Imp, vCPU_AbsY,  vCPU_Imp,     vCPU_AbsYW, vCPU_AbsX,    vCPU_AbsX,  vCPU_AbsXW,      vCPU_AbsXW, //5
    vCPU_Imp, vCPU_IndX, vCPU_None, vCPU_IndX,  vCPU_Zero,  vCPU_Zero,  vCPU_Zero,       vCPU_Zero,  vCPU_Imp, vCPU_Imm,   vCPU_Acc,     vCPU_Imm,   vCPU_Ind,     vCPU_Abs,   vCPU_Abs,        vCPU_Abs,   //6
    vCPU_Rel, vCPU_IndY, vCPU_None, vCPU_IndYW, vCPU_ZeroX, vCPU_ZeroX, vCPU_ZeroX,      vCPU_ZeroX, vCPU_Imp, vCPU_AbsY,  vCPU_Imp,     vCPU_AbsYW, vCPU_AbsX,    vCPU_AbsX,  vCPU_AbsXW,      vCPU_AbsXW, //7

    vCPU_Imm, vCPU_IndX, vCPU_Imm,  vCPU_IndX,  vCPU_Zero,  vCPU_Zero,  vCPU_Zero,       vCPU_Zero,  vCPU_Imp, vCPU_Imm,   vCPU_Imp,     vCPU_Imm,   vCPU_Abs,     vCPU_Abs,   vCPU_Abs,        vCPU_Abs,   //8
    vCPU_Rel, vCPU_IndYW,vCPU_None, vCPU_IndYW, vCPU_ZeroX, vCPU_ZeroX, vCPU_ZeroY,      vCPU_ZeroY, vCPU_Imp, vCPU_AbsYW, vCPU_Imp,     vCPU_AbsYW, vCPU_AbsXW,   vCPU_AbsXW, vCPU_AbsYW,      vCPU_AbsYW, //9
    vCPU_Imm, vCPU_IndX, vCPU_Imm,  vCPU_IndX,  vCPU_Zero,  vCPU_Zero,  vCPU_Zero,       vCPU_Zero,  vCPU_Imp, vCPU_Imm,   vCPU_Imp,     vCPU_Imm,   vCPU_Abs,     vCPU_Abs,   vCPU_Abs,        vCPU_Abs,   //A
    vCPU_Rel, vCPU_IndY, vCPU_None, vCPU_IndY,  vCPU_ZeroX, vCPU_ZeroX, vCPU_ZeroY,      vCPU_ZeroY, vCPU_Imp, vCPU_AbsY,  vCPU_Imp,     vCPU_AbsY,  vCPU_AbsX,    vCPU_AbsX,  vCPU_AbsY,       vCPU_AbsY,  //B

    vCPU_Imm, vCPU_IndX, vCPU_Imm,  vCPU_IndX,  vCPU_Zero,  vCPU_Zero,  vCPU_Zero,       vCPU_Zero,  vCPU_Imp, vCPU_Imm,   vCPU_Imp,     vCPU_Imm,   vCPU_Abs,     vCPU_Abs,   vCPU_Abs,        vCPU_Abs,   //C
    vCPU_Rel, vCPU_IndY, vCPU_None, vCPU_IndYW, vCPU_ZeroX, vCPU_ZeroX, vCPU_ZeroX,      vCPU_ZeroX, vCPU_Imp, vCPU_AbsY,  vCPU_Imp,     vCPU_AbsYW, vCPU_AbsX,    vCPU_AbsX,  vCPU_AbsXW,      vCPU_AbsXW, //D
    vCPU_Imm, vCPU_IndX, vCPU_Imm,  vCPU_IndX,  vCPU_Zero,  vCPU_Zero,  vCPU_Zero,       vCPU_Zero,  vCPU_Imp, vCPU_Imm,   vCPU_Imp,     vCPU_Imm,   vCPU_Abs,     vCPU_Abs,   vCPU_Abs,        vCPU_Abs,   //E
    vCPU_Rel, vCPU_IndY, vCPU_None, vCPU_IndYW, vCPU_ZeroX, vCPU_ZeroX, vCPU_ZeroX,      vCPU_ZeroX, vCPU_Imp, vCPU_AbsY,  vCPU_Imp,     vCPU_AbsYW, vCPU_AbsX,    vCPU_AbsX,  vCPU_AbsXW,      vCPU_AbsXW, //F
};

const uint32_t CPUClockTable[256] = {
    7, 6, 2, 8,   3, 3, 5, 5,   3, 2, 2, 2,   4, 4, 6, 6,
    2, 5, 2, 8,   4, 4, 6, 6,   2, 4, 2, 7,   4, 4, 7, 7,
    6, 6, 2, 8,   3, 3, 5, 5,   4, 2, 2, 2,   4, 4, 6, 6,
    2, 5, 2, 8,   4, 4, 6, 6,   2, 4, 2, 7,   4, 4, 7, 7,

    6, 6, 2, 8,   3, 3, 5, 5,   3, 2, 2, 2,   3, 4, 6, 6,
    2, 5, 2, 8,   4, 4, 6, 6,   2, 4, 2, 7,   4, 4, 7, 7,
    6, 6, 2, 8,   3, 3, 5, 5,   4, 2, 2, 2,   5, 4, 6, 6,
    2, 5, 2, 8,   4, 4, 6, 6,   2, 4, 2, 7,   4, 4, 7, 7,

    2, 6, 2, 6,   3, 3, 3, 3,   2, 2, 2, 2,   4, 4, 4, 4,
    2, 6, 2, 6,   4, 4, 4, 4,   2, 5, 2, 5,   5, 5, 5, 5,
    2, 6, 2, 6,   3, 3, 3, 3,   2, 2, 2, 2,   4, 4, 4, 4,
    2, 5, 2, 5,   4, 4, 4, 4,   2, 4, 2, 4,   4, 4, 4, 4,

    2, 6, 2, 8,   3, 3, 5, 5,   2, 2, 2, 2,   4, 4, 6, 6,
    2, 5, 2, 8,   4, 4, 6, 6,   2, 4, 2, 7,   4, 4, 7, 7,
    2, 6, 3, 8,   3, 3, 5, 5,   2, 2, 2, 2,   4, 4, 6, 6,
    2, 5, 2, 8,   4, 4, 6, 6,   2, 4, 2, 7,   4, 4, 7, 7,
};



/* FetchOperand */
inline static uint16_t GetIndAddr(void)
{
    uint16_t lo = ucNesCPUMemoryGet(CPU_PC++), hi = ucNesCPUMemoryGet(CPU_PC++);

    return lo | hi << 8;
}

inline static uint8_t GetImmediate(void)
{
    return ucNesCPUMemoryGet(CPU_PC++);
}

inline static uint8_t GetZeroAddr(void)
{
    return ucNesCPUMemoryGet(CPU_PC++);
}

inline static uint8_t GetZeroXAddr(void)
{
    return ucNesCPUMemoryGet(CPU_PC++) + CPU_X;
}

inline static uint8_t GetZeroYAddr(void)
{
    return ucNesCPUMemoryGet(CPU_PC++) + CPU_Y;
}

inline static uint16_t GetAbsAddr(void)
{
    uint16_t lo = ucNesCPUMemoryGet(CPU_PC++), hi = ucNesCPUMemoryGet(CPU_PC++);

    return lo | hi << 8;
}

inline static uint16_t GetAbsXAddr(void)
{
    uint16_t lo = ucNesCPUMemoryGet(CPU_PC++), hi = ucNesCPUMemoryGet(CPU_PC++), value = lo | hi << 8;

    return value + CPU_X;
}

inline static uint16_t GetAbsYAddr(void)
{
    uint16_t lo = ucNesCPUMemoryGet(CPU_PC++), hi = ucNesCPUMemoryGet(CPU_PC++), value = lo | hi << 8;

    return value + CPU_Y;
}

inline static uint16_t GetIndXAddr(void)
{
    uint8_t zero = ucNesCPUMemoryGet(CPU_PC++) + CPU_X;
    uint16_t addr = (zero == 0xFF) ? (ucNesCPUMemoryGet(0xFF) | ((uint16_t)ucNesCPUMemoryGet(0x00)) << 8) : ucNesCPUMemoryWordGet(zero);

    return addr;
}

inline static uint16_t GetIndYAddr(void)
{
    uint8_t zero = ucNesCPUMemoryGet(CPU_PC++);
    uint16_t addr = (zero == 0xFF) ? (ucNesCPUMemoryGet(0xFF) | ((uint16_t)ucNesCPUMemoryGet(0x00)) << 8) : ucNesCPUMemoryWordGet(zero);

    return addr + CPU_Y;
}

inline static uint32_t FetchOperand(ModeEnum enumValue)
{
    switch(enumValue)
    {
        case vCPU_Acc:
        case vCPU_Imp:   return 0;
        case vCPU_Imm:
        case vCPU_Rel:   return GetImmediate();
        case vCPU_Zero:  return GetZeroAddr();
        case vCPU_ZeroX: return GetZeroXAddr();
        case vCPU_ZeroY: return GetZeroYAddr();
        case vCPU_Ind:   return GetIndAddr();
        case vCPU_IndX:  return GetIndXAddr();
        case vCPU_IndY:
        case vCPU_IndYW: return GetIndYAddr();
        case vCPU_Abs:   return GetAbsAddr();
        case vCPU_AbsX:
        case vCPU_AbsXW: return GetAbsXAddr();
        case vCPU_AbsY:
        case vCPU_AbsYW: return GetAbsYAddr();

        default: return 0;
    }
}


inline static void vNesCPUIrq(void)
{
    PushWord(CPU_PC);

    Push((CPU_PS & ~Break) | Reserved);
    SetFlags(Interrupt);

    if(st_typeNesCPUInfo.NMIFlag == TRUE)
    {
        CPU_PC = ucNesCPUMemoryWordGet(NMIVector);

        st_typeNesCPUInfo.NMIFlag = FALSE;
    }
    else if(st_typeNesCPUInfo.IRQFlag == TRUE)
    {
        CPU_PC = ucNesCPUMemoryWordGet(IRQVector);

        st_typeNesCPUInfo.IRQFlag = FALSE;
    }
    else if(st_typeNesCPUInfo.ResetFlag == TRUE)
    {
        CPU_PC = ucNesCPUMemoryWordGet(ResetVector);

        st_typeNesCPUInfo.ResetFlag = FALSE;
    }

    /* 中断耗7个CPU时钟 */
    st_typeNesCPUInfo.clockCnt += 7;
}

void vNesCPUStart(void)
{
    //Use _memoryManager->Read() directly to prevent clocking the PPU/APU when setting PC at reset
    //st_typeNesCPUInfo.registers.PC = 0xC004;

    memset(&st_typeNesCPUInfo.registers, 0, sizeof(st_typeNesCPUInfo.registers));
    st_typeNesCPUInfo.registers.SP = 0xFF;
    st_typeNesCPUInfo.registers.PS = Reserved;
    st_typeNesCPUInfo.IRQFlag = FALSE;
    st_typeNesCPUInfo.NMIFlag = FALSE;
    st_typeNesCPUInfo.ResetFlag = TRUE;

    st_typeNesCPUInfo.clockCnt = 0;

    vNesCPUIrq();

    printf("A: 0x%02X  X: 0x%02X  Y: 0x%02X  PS: 0x%02X  SP: 0x%02X  PC: 0x%04X\r\n", st_typeNesCPUInfo.registers.A, st_typeNesCPUInfo.registers.X,\
                                                                                    st_typeNesCPUInfo.registers.Y, st_typeNesCPUInfo.registers.PS,\
                                                                                    st_typeNesCPUInfo.registers.SP, st_typeNesCPUInfo.registers.PC);
}

void vNesCPUInit(void)
{
    st_typeNesCPUInfo.RAM  = (uint8_t *)&st_ucCPU_RAM[0];
    st_typeNesCPUInfo.SRAM = (uint8_t *)&st_ucCPU_SRAM[0];
}

void vNesCPUExec(void)
{
    /* NTAR格式时，PPU的时钟为CPU的3倍 */
    while((g_typeNesInfo.ptypeCPUInfo->clockCnt * 3) < g_typeNesInfo.ptypePPUInfo->clockCnt)
    {
        /* 读取PC指向的指令 */
        st_typeNesCPUInfo.opCode = ucNesCPUMemoryGet(CPU_PC++);

        /* 获取模式 */
        st_typeNesCPUInfo.Mode = InstructionModeTable[st_typeNesCPUInfo.opCode];
        /* 获取操作数 */
        st_typeNesCPUInfo.operand = FetchOperand(st_typeNesCPUInfo.Mode);
        /* 获取指令 */
        st_typeNesCPUInfo.Function = InstructionFunctionTable[st_typeNesCPUInfo.opCode];

//            printf("%04X, %02X, %02X, %04X\r\n", st_typeNesCPUInfo.registers.PC, st_typeNesCPUInfo.opCode, st_typeNesCPUInfo.Mode, st_typeNesCPUInfo.operand);
//            printf("A:%02X,X:%02X,Y:%02X,PS:%02X,SP:%02X\r\n\n",
//                st_typeNesCPUInfo.registers.A,
//                st_typeNesCPUInfo.registers.X,
//                st_typeNesCPUInfo.registers.Y,
//                st_typeNesCPUInfo.registers.PS,
//                st_typeNesCPUInfo.registers.SP);

        /* 执行指令 */
        st_typeNesCPUInfo.Function();

        /* 获取当前指令所耗CPU时钟 */
        st_typeNesCPUInfo.clockCnt += CPUClockTable[st_typeNesCPUInfo.opCode];

        /* 执行中断 */
        if(st_typeNesCPUInfo.IRQFlag || st_typeNesCPUInfo.NMIFlag)
        {
            vNesCPUIrq();
        }
    }
}

NesCPUInofType *ptyeNesCPUInfoGet(void)
{
    return &st_typeNesCPUInfo;
}
